import re

from django.contrib.auth import authenticate
from django.contrib.auth import login as django_login
from django.contrib.auth import logout as django_logout
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, get_list_or_404

from settings import ADMIN_BASE_URL, IMAGES_ROOT_FOLDER

from common import get_related_objects, djips_view
from core import sync_filesystem, find_root
from models import Lang, Folder, Color, ColorAttr
from models import Tag, TagAttr, Image, ImageAttr

template_folder = 'djips/common/'

@djips_view('Anonymous')
def login(request):
    """ Allow users to log in.
    """
    params = {}
    if 'next' in request.GET:
	params['next'] = request.GET['next']

    if request.method == 'POST':
	user = authenticate(username=request.POST['login'],
			    password=request.POST['password'])
        
	if user is not None:
	    if user.is_active:
		django_login(request, user)

		if 'next' in request.POST:
		    return HttpResponseRedirect(request.POST['next'])

		return HttpResponseRedirect(ADMIN_BASE_URL + '/djips_common/')
	    else:
		params = {'disabled_account': True}
	else:
	    params = {'login_failed': True}

    return 'login.html', params

@djips_view('Admin')
def logout(request):
    """ Logs the user out.
    """
    django_logout(request)
    return 'login.html', {'logged_out': True}

@djips_view('Admin')
def index(request):
    """ Default page for Djips admin and Djips_common admin.
    """
    return 'index.html', {}

@djips_view('Admin')
def index_redirect(request):
    """ Redirect to djips_common index.
    """
    return HttpResponseRedirect(ADMIN_BASE_URL + '/djips_common/')


@djips_view('Admin')
def synchronize(request, folder_id=''):
    """ Page used to synchronize filtesystem and database.
    """
    root = find_root()
    
    filesystem_link = ADMIN_BASE_URL + '/djips_common/synchronize/%s/'
    filesystem_options = {'url': filesystem_link}

    if not folder_id:
	return 'synchronize.html', {'root': root,
                                    'filesystem_options': filesystem_options}

    if folder_id == '-1':
	folder = None
	path = IMAGES_ROOT_FOLDER
    else:
	folder = get_object_or_404(Folder, pk = folder_id)
	path = folder.dir

    nf, ni, df, di = sync_filesystem(path)
    filesystem_options['current_folder'] = folder
    filesystem_options['new_folders'] = nf

    if not root and nf:
	root = find_root()

    return 'synchronize.html', {'synchronized': True,
                                'new_folders': nf,
                                'new_images': ni,
                                'deleted_folders': df,
                                'deleted_images': di,
                                'root': root,
                                'filesystem_options': filesystem_options}

@djips_view('Admin')
def manage_languages(request):
    """ Page to manage languages installed.
    """
    langs = Lang.objects.all()
    
    # Dictionnary containing default values or values set by the user.
    values = []

    if request.method == 'GET' or \
	    (request.method == 'POST' and 'cancel' in request.POST):
	# Just send the default values.
	values = langs
	return 'languages.html', {'values': values}

    form = request.POST
    errors = False

    # We update (or try to) update each language.
    for lang in langs:
	if not 'name_' + lang.id in form:
	    # Should not append except if the user modifies the HTML code.
	    values.append(lang)
	    continue

	values.append({})

	is_default = 'default' in form and form['default'] == str(lang.id)
	is_activated =  'activated_' + lang.id in form
	name = form['name_' + lang.id]

	if not name:
	    errors = True
	    values[-1]['empty_name'] = True
	elif len(name) > 50:
	    errors = True
	    values[-1]['too_long_name'] = True
	else:
	    lang.name = name
	    lang.default = is_default
	    lang.activated = is_activated
	    lang.save()
	    values[-1] = lang
	    continue

	# We keep the values set by the user.
	values[-1]['id'] = lang.id
	values[-1]['name'] = name
	values[-1]['default'] = is_default
	values[-1]['activated'] = is_activated
	
    # We add the new language.
    new_values = {}
    new_errors = False

    if 'new_id' in form and 'new_name' in form and \
	(form['new_id'] or form['new_name']):

	new_id = form['new_id']
	new_name = form['new_name']
	new_default = 'default' in form and form['default'] == 'new_lang'
	new_activated = 'activated_new' in form

	# We validate the new id. It must have a length between 1 and 5
	# and not be used already.
	if not new_id:
	    new_errors = True
	    new_values['empty_id'] = True
	elif len(new_id) > 5:
	    new_errors = True
	    new_values['too_long_id'] = True
	else:
	    try:
		Lang.objects.get(id = new_id)
		new_errors = True
		new_values['used_id'] = True
	    except Lang.DoesNotExist:
		# The expected behaviour is that the error is raised.
		pass

	# We validate the name. Its name must be between 1 and 50 characters.
	if not new_name:
	    new_errors = True
	    new_values['empty_name'] = True
	elif len(new_name) > 50:
	    new_errors = True
	    new_values['too_long_name'] = True
	    
	if new_errors:
	    # We send back the values provided.
	    for field in ['id', 'name', 'default', 'activated']:
		new_values[field] = eval('new_' + field)
	else:
	    new_lang = Lang(id = new_id,
			    name = new_name,
			    default = new_default,
			    activated = new_activated)
	    new_lang.save()
	    values.append(new_lang)

    return 'languages.html', {'values': values,
                              'new': new_values,
                              'update_errors': errors,
                              'add_errors': new_errors,
                              'success': not (errors or new_errors)}

@djips_view('Admin')
def manage_colors(request):
    """ Page to create/update colors.
    """
    colors = Color.objects.all()
    values = []

    if request.method == 'GET'  or \
	    (request.method == 'POST' and 'cancel' in request.POST):
        if 'deleted' in request.GET:
            deleted_name = request.GET['deleted']
        else:
            deleted_name = None
	return 'colors.html', {'values': colors,
                               'deleted': deleted_name}

    form = request.POST
    errors = False
    valid_color = re.compile(r'^[a-fA-F0-9]{6}$')

    for color in colors:
	if not 'value_' + str(color.id) in form or \
	    not 'name_' + str(color.id) in form:
	    # Should not happen.
	    values.append(color)
	    errors = True
	    continue

	values.append({})

	value = form['value_' + str(color.id)]

	# Removes the first # is present.
        if value.startswith('#'):
	    value = value[1:]

	name = form['name_' + str(color.id)]

	if not valid_color.match(value):
	    errors = True
	    values[-1]['invalid_value'] = True

	if not name:
	    errors = True
	    values[-1]['empty_name'] = True
	elif len(name) > 100:
	    errors = True
	    values[-1]['too_long_name'] = True
	    
	if errors:
	    values[-1]['id'] = color.id
	    values[-1]['name'] = name
	    values[-1]['value'] = value
	    continue

	color.value = value
	color.name = name
	color.save()

	values[-1] = color

    # Now we try to add a new color
    new_errors = []
    new_values = {}

    if 'new_value' in form and 'new_name' in form and \
	(form['new_value'] or form['new_name']):

	new_value = form['new_value']
	# Removes the first # is present.
        if new_value.startswith('#'):
	    new_value = new_value[1:]

	new_name = form['new_name']

	if not valid_color.match(new_value):
	    new_errors = True
	    new_values['invalid_value'] = True
	    
	if not new_name:
	    new_errors = True
	    new_values['empty_name'] = True
	elif len(new_name) > 100:
	    new_errors = True
	    new_values['too_long_name'] = True

	if new_errors:
	    new_values['name'] = new_name
	    new_values['value'] = new_value
	else:
	    new_color = Color(value = new_value)
	    new_color.save()
	    new_color.name = new_name

	    values.append(new_color)

    return 'colors.html', {'values': values,
                           'new': new_values,
                           'update_errors': errors,
                           'add_errors': new_errors,
                           'success': not (errors or new_errors)}
    
@djips_view('Admin')
def delete_color(request, color_id):
    """ Page called to confirm deletion of a color.
    """
    color = get_object_or_404(Color, pk = color_id)
    color_name = color.name
    color_related_objects = get_related_objects(color)

    if request.method == 'GET':
        return 'delete_color.html', {'color': color,
                                     'related_objects': color_related_objects}

    deleted = False
    if 'confirm' in request.POST:
        color.delete()
        deleted = True

    return HttpResponseRedirect(ADMIN_BASE_URL + \
                                '/djips_common/colors/' + \
                                (deleted and '?deleted=%s'%color_name or ''))

@djips_view('Admin')
def manage_images(request, folder_id=''):
    """ Page used to update multiple images at the same time.
    It can be used to update:
     - title (in the default language)
     - description (in the default language)
     - mark
     - color flag
     - tags
    """
    root = find_root()

    if not root:
	# File system has never been synchronized.
	return 'images.html', {'no_sync': True,
                               'root': None}

    if folder_id:
	folder = get_object_or_404(Folder, pk = folder_id)
	# We get all images contained in the folder (including those
	# in sub-folders).
	images = folder.all_images()
    else:
	folder = None
	images = []

    colors = Color.objects.all()
    filesystem_link = ADMIN_BASE_URL + '/djips_common/images/%s/'
    filesystem_options = {'url': filesystem_link,
		       'current_folder': folder}

    if request.method == 'GET' or \
	    (request.method == 'POST' and 'cancel' in request.POST):
	return 'images.html', {'root': root,
                               'values': images,
                               'colors': colors,
                               'current_folder': folder,
                               'filesystem_options': filesystem_options}

    errors = False
    form = request.POST

    for image in images:
	# Checks that fields have been correctly submitted.
	for field in ['title', 'description', 'mark', 'tags', 'color']:
	    if not field + '_' + str(image.id) in form:
		# Should not happen except if HTML has been modified.
		errors = True
		continue

	# We get the new values.
	image.title = form['title_' + str(image.id)]
	image.description = form['description_' + str(image.id)]
	try:
	    image.mark = int(form['mark_' + str(image.id)])
	except ValueError:
	    # Should not happen.
	    image.mark = 0

	if form['color_' + str(image.id)] == '-':
	    color = None
	else:
	    try:
		color = Color.objects.get(id = form['color_' + str(image.id)])
	    except Color.DoesNotExist:
		color = None

	image.color_flag = color
	image.update_tags(form['tags_' + str(image.id)])

	image.save()

    return 'images.html', {'root': root,
                           'current_folder': folder,
                           'values': images,
                           'colors': colors,
                           'errors': errors,
                           'success': not errors,
                           'filesystem_options': filesystem_options}
	    
@djips_view('Admin')
def manage_image(request, image_id):
    """ Page used to manage all informations about a single image.
    """
    img = get_object_or_404(Image, pk = image_id)
    parent = img.folder
    langs = Lang.objects.filter(activated=True)
    colors = Color.objects.all()
    
    if request.method == 'GET' or \
	    (request.method == 'POST' and 'cancel' in request.POST):
	form = {'id': img.id,
		'file': img.file,
		'mark': img.mark,
		'color': img.color_flag,
		'tags': img.tags_as_list(),
		'titles' : {},
		'descriptions': {}}
    
	for lang in langs:
	    try:
		attr = ImageAttr.objects.get(image = img,
					     lang = lang)
		form['titles'][lang.id] = attr.title
		form['descriptions'][lang.id] = attr.description
	    except ImageAttr.DoesNotExist:
		# There is no ImageAttr for this lang yet, it will
		# be created when saving.
		pass

	return 'image.html', {'img': form,
                              'parent': parent,
                              'colors': colors,
                              'langs': langs}

    form = request.POST.copy()
    # Some stuff that are not transmitted by the form.
    form['id'] = img.id
    form['file'] = img.file
    form['titles'] = {}
    form['descriptions'] = {}
    errors = False

    # Fields that should be present in the form.
    fields = ['mark', 'color', 'tags']    
    for lang in langs:
	fields.extend(['title_' + lang.id,
		       'description_' + lang.id])

    for field in fields:
	if not field in form:
	    # Should not happen, except if HTML has been modified.
	    # XXX - return an error page ? which one ? one that says
	    # 'Stop modifying HTML before submitting you fuckin' morron' ?
	    return
    
    try:
	img.mark = int(form['mark'])
    except ValueError:
	# Should not happen neither.
	pass

    try:
	img.color_flag = Color.objects.get(id = form['color'])
    except (Color.DoesNotExist, ValueError):
	# Happens when '--' is selected.
	img.color_flag = None

    # We have to send back the color object.
    form['color'] = img.color_flag

    img.update_tags(form['tags'])
    # Simplify the tag list
    form['tags'] = img.tags_as_list()

    img.save()

    for lang in langs:
	title = form['title_' + lang.id]
	desc = form['description_' + lang.id]

	form['titles'][lang.id] = title
	form['descriptions'][lang.id] = desc

	try:
	    attr = ImageAttr.objects.get(image=img,
					 lang=lang)
	except ImageAttr.DoesNotExist:
	    # Ok, we create one.
	    attr = ImageAttr(image = img,
			     lang = lang)

	attr.title = title
	attr.description = desc
	attr.save()

    return 'image.html', {'img': form,
                          'parent': parent,
                          'colors': colors,
                          'langs': langs,
                          'success': True}

def _mass_translate(request, objects, langs, attr_type,
		    ref_name, extra_fields = {}):
    """ Updates translations for all tags/colors (might be applicable to
    other kind of objects in the future.

    It takes 5 parameters:
    - request: the request object passed to the view calling this page.
    - objects: the list of objects to translate
    - langs: the list of languages
    - attr_type: the Model used to store translations
    - ref_name: the name used by attr_type to reference the translated object.
                this name is also used to prefix values in the form.
    - extra_field: a dictionnary used to add extra fields for each value.
      if extra_fields equals {'grep': 'blabla()'}, each returned row
      will contain a 'grep' field, which value is 'object.blabla()'

    It returns a list of dictionnaries looking like this:
    [{'id': 1, 'en': 'Animals', 'fr': 'Animaux'}
     {'id': 2, 'en': 'Plants', 'fr': 'Plantes'}]
    """
    values = []
    form = request.POST
    for obj in objects:
	values.append({'id': obj.id})

	for lang in langs:
	    existing_attr = False
	    try:
		attr = eval(attr_type.__name__ + '.objects.get(lang=lang,' + \
			    ref_name + '=obj)')
		existing_attr = True
	    except attr_type.DoesNotExist:
		# We automatically create one, but we only save 
		# it in the database if a new value has been provided.
		attr = eval(attr_type.__name__ + '(lang=lang,' + \
			                  ref_name + '=obj)')

	    # Check that a value has been submitted.
	    field = ref_name + '_' + str(obj.id) + '_' + str(lang.id)
	    if field in form and request.method == 'POST':
		# We only update and save the value if an attribute was
		# existing or that a translation has been provided.
		if form[field] or existing_attr:
		    attr.name = form[field]
		    attr.save()

	    values[-1][lang.id] = attr.name

	    for extra in extra_fields:
		try:
		   values[-1][extra] = eval('obj.' + extra_fields[extra])
		except:
		    pass
    return values


@djips_view('Admin')
def manage_translations(request):
    """ Page used to create or update translations for Colors and Tags.
    Images translations are managed with the 'manage_image' view.
    """
    colors = Color.objects.all()
    langs = Lang.objects.filter(activated=True)

    # For tags, we build a list tha respects the hierarchy in order
    # to give some context to the user (always usefull for translations)
    tags = []

    for tag in Tag.objects.filter(father_tag = None):
	path = [tag]
	while path:
	    current_tag = path.pop()
	    tags.append(current_tag)

	    path.extend(current_tag.son_tags.all())

    color_values = _mass_translate(request, colors, langs, ColorAttr, 'color')
    tag_values = _mass_translate(request, tags, langs, TagAttr, 'tag', 
				 {'ancestors': 'ancestors()',
				  'default_name': 'name'})
    
    return 'translations.html', {'colors': color_values,
                                 'tags': tag_values,
                                 'langs': langs,
                                 'success': request.method == 'POST'}
